#ifndef PCREPP_HPP
#define PCREPP_HPP

#include "../pcre.h"
#include "../pcreposix.h"
#include "convertutf.hpp"

#if defined _MFC_BULID || defined __AFXWIN_H__
#include <afxtempl.h>
#endif

#define DFT_BUFFER 30

#define PCRE_COMP_MASK	(PCRE_ANCHORED | PCRE_AUTO_CALLOUT | PCRE_CASELESS | PCRE_DOLLAR_ENDONLY | PCRE_DOTALL | PCRE_EXTENDED | PCRE_EXTRA | PCRE_MULTILINE | PCRE_NO_AUTO_CAPTURE | PCRE_UNGREEDY | PCRE_UTF8 | PCRE_NO_UTF8_CHECK)
#define PCRE_EXEC_MASK	(PCRE_ANCHORED | PCRE_NOTBOL | PCRE_NOTEOL | PCRE_NOTEMPTY | PCRE_NO_UTF8_CHECK | PCRE_PARTIAL)

#define RP_ERR_PATTERN		1
#define RP_ERR_VECTOR		2
#define RP_ERR_SUBSTR		3
#define RP_ERR_BUFFER		4

namespace PCRE {

class replacef {
public:
	replacef();
	~replacef();
	void Attach(const char *replace_str);
	void Detach();
	size_t ParseLength();
	void Parse(char *result);
	void SetSubstringList(char **substr_list, int count);
private:
	char **substr_set;
	int substr_count;
	char *format_str;
	int *format_vector;
	int valid_elem;
};

char *str_realloc(char *&oldBuffer, const size_t newSize);

template <class CHART1, class CHART2>
bool match(const CHART1 *pattern, const CHART2 *subject, int flags = 0)
{
	const size_t ts1 = sizeof(CHART1), ts2 = sizeof(CHART2);

	if (!IS_UTF(CHART1) || !IS_UTF(CHART2) || pattern == NULL || subject == NULL)
		return false;

	char *u8pattern = (char *) pattern, *u8subject = (char *) subject;
	char alloc = 0;

	if (ts1 > 1) {
		ConvertUTF(pattern, u8pattern);
		flags |= PCRE_UTF8;
		alloc |= 0x01;
	}
	if (ts2 > 1) {
		ConvertUTF(subject, u8subject);
		flags |= PCRE_UTF8;
		alloc |= 0x02;
	}

	pcre *hRx;
	const char *errmsg;
	int erroffset, obuffer[DFT_BUFFER];
	bool match_result;

	if ((hRx = pcre_compile(u8pattern, flags & PCRE_COMP_MASK, &errmsg, &erroffset, NULL)) == NULL)
		match_result = false;
	else {
		match_result = (pcre_exec(hRx, NULL, u8subject, strlen(u8subject), 0, flags & PCRE_EXEC_MASK, obuffer, DFT_BUFFER) >= 0);
		free(hRx);
	}

	if (alloc & 0x01)
		delete [] u8pattern;
	if (alloc & 0x02)
		delete [] u8subject;

	return match_result;
}

template <class CHART1, class CHART2, class CHART3, class CHART4>
int replace(const CHART1 *pattern, const CHART2 *replacement, const CHART3 *subject, CHART4 *&result, size_t length = 0, int flags = 0)
{
	const size_t ts1 = sizeof(CHART1), ts2 = sizeof(CHART2), ts3 = sizeof(CHART3), ts4 = sizeof(CHART4);

	if (!IS_UTF(CHART1) || !IS_UTF(CHART2) || !IS_UTF(CHART3) || !IS_UTF(CHART4) || pattern == NULL || subject == NULL)
		return false;

	char *u8pattern = (char *) pattern, *u8replacement = (char *) replacement, *u8subject = (char *) subject, *u8result = NULL;
	char alloc = 0;
	size_t buflen;

	if (ts1 > 1) {
		ConvertUTF(pattern, u8pattern);
		flags |= PCRE_UTF8;
		alloc |= 0x01;
	}
	if (ts2 > 1) {
		ConvertUTF(replacement, u8replacement);
		flags |= PCRE_UTF8;
		alloc |= 0x02;
	}
	if (ts3 > 1) {
		ConvertUTF(subject, u8subject);
		flags |= PCRE_UTF8;
		alloc |= 0x04;
	}
	const size_t subject_len = strlen(u8subject);
	if (ts4 > 1) {
		if (length > 0)
			buflen = length + 1;
		else
			buflen = subject_len << 1;
		u8result = new char [buflen];
		flags |= PCRE_UTF8;
		alloc |= 0x08;
	}
	else if (length == 0) {
		buflen = subject_len << 1;
		u8result = new char [buflen];
		alloc |= 0x08;
	}
	else
		buflen = length;

	pcre *hRx;
	const char *errmsg;
	int erroffset, obuffer[DFT_BUFFER], count, s_start, opts, o_start = 0;
	int err_code = 0;

	try {
		if ((hRx = pcre_compile(u8pattern, flags & PCRE_COMP_MASK, &errmsg, &erroffset, NULL)) == NULL)
			throw RP_ERR_PATTERN;

		replacef rp;

		rp.Attach(u8replacement);

		obuffer[0] = -1;
		obuffer[1] = 0;
		*u8result = '\0';
		for (s_start = 0;s_start < subject_len;s_start = obuffer[1]) {
			opts = flags & PCRE_EXEC_MASK;

			if (obuffer[0] == obuffer[1]) {
				if (obuffer[0] == subject_len)
					break;
				opts |= PCRE_NOTEMPTY | PCRE_ANCHORED;
			}

			count = pcre_exec(hRx, NULL, u8subject, subject_len, s_start, opts, obuffer, DFT_BUFFER);
			if (count > 0) { //match successful
				char **substr_list;
				size_t t_len;

				if (pcre_get_substring_list(u8subject, obuffer, count, (const char ***) &substr_list))
					throw RP_ERR_SUBSTR;
				rp.SetSubstringList(substr_list, count);

				t_len = rp.ParseLength();

				if (o_start + obuffer[0] - s_start + t_len >= buflen) { //output buffer is not enough
					if (length > 0)
						throw RP_ERR_BUFFER;

					//reallocate output buffer
					buflen = o_start + obuffer[0] - s_start + t_len + subject_len;
					str_realloc(u8result, buflen);
				}

				strncpy(u8result + o_start, u8subject + s_start, obuffer[0] - s_start);
				o_start += obuffer[0] - s_start;
				rp.Parse(u8result + o_start);
				o_start += t_len;

				pcre_free_substring_list((const char **) substr_list);
			}
			else if (count == 0) //output vector is not enough
				throw RP_ERR_VECTOR;
			else if (count == PCRE_ERROR_NOMATCH) { //match failed
				if ((opts & PCRE_NOTEMPTY) == 0)
					break;
				obuffer[1] = s_start + 1;
			}
			else //other exceptions
				throw count;
		}

		if (o_start + subject_len - s_start >= buflen) { //output buffer is not enough
			if (length > 0)
				throw RP_ERR_BUFFER;

			//reallocate output buffer
			buflen = o_start + subject_len - s_start + 2;
			str_realloc(u8result, buflen);
		}
		strcpy(u8result + o_start, u8subject + s_start);
	} catch (int ex) {
		err_code = ex;
	}

	if (ts4 > 1) {
		if (length > 0)
			ConvertUTF(u8result, result, length);
		else
			ConvertUTF(u8result, result);
	}
	else
		result = (CHART4 *) u8result;

	if (hRx)
		free(hRx);
	if (alloc & 0x01)
		delete [] u8pattern;
	if (alloc & 0x02)
		delete [] u8replacement;
	if (alloc & 0x04)
		delete [] u8subject;
	if ((alloc & 0x08) && (length > 0 || ts4 > 1))
		delete [] u8result;

	return err_code;
}

#if defined _MFC_BULID || defined __AFXWIN_H__
int replace(const char *pattern, const char *replacement, CStringA &target, int flags = 0);
int replace(const wchar_t *pattern, const wchar_t *replacement, CStringW &target, int flags = 0);
#endif

} //END namespace PCRE

#endif
